www.gusucode.com > VC++ 曲线的绘制-源码程序 > VC++ 曲线的绘制-源码程序/code/LinePlot.cpp

    //Download by http://www.NewXing.com
// LinePlot.cpp : implementation file
/////////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////////
//  CLinePlot, CPlotData - a simple 2D graph control
//
//  Author: Paul Grenz
//  Email:  pgrenz@irlabs.com
//
//  You may freely use or modify this code provided this
//  message is included in all derived versions.
//
//  History - 2004/10/28: Initial release to codeguru.com
//
//
//  This class implements a simple 2D graph control which supports
//  multiple plots, locking and unlocking of axes, printing, and 
//  zooming.
/////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "LinePlotTest.h"
#include "LinePlot.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CPlotData class
/////////////////////////////////////////////////////////////////////////////

CPlotData::CPlotData(CString szName, COLORREF crColor, int nStyle, std::vector<FLOATPOINT> *pvecData)
{
	m_szName = szName;
  m_nStyle = nStyle;
  m_oIsDirty = true;
  m_rcKeyArea.SetRectEmpty();
  m_crColor = crColor;
  m_eXDataMin = 0.0f;
  m_eXDataMax = 0.0f;
  m_eYDataMin = 0.0f;
  m_eYDataMax = 0.0f;
  m_eYDataMean = 0.0f;
  m_pptData = NULL;
  m_pptScreen = NULL;
  m_uiPointCount = 0;

  //  copy the point data over, calculating max & min.
  UINT uiPointCount = pvecData->size();
  if (uiPointCount>0)
  {
    m_pptData = new FLOATPOINT[uiPointCount];
    m_pptScreen = new POINT[uiPointCount];
    m_uiPointCount = uiPointCount;
    if (m_pptData!=NULL && m_pptScreen!=NULL)
    {
      UpdateData(pvecData);
    }
  }
}
/////////////////////////////////////////////////////////////////////////////

CPlotData::CPlotData(CString szName, COLORREF crColor, int nStyle, FLOATPOINT *pptData, UINT uiPointCount)
{
	m_szName = szName;
  m_nStyle = nStyle;
  m_oIsDirty = true;
  m_rcKeyArea.SetRectEmpty();
  m_crColor = crColor;
  m_eXDataMin = 0.0f;
  m_eXDataMax = 0.0f;
  m_eYDataMin = 0.0f;
  m_eYDataMax = 0.0f;
  m_eYDataMean = 0.0f;
  m_pptData = NULL;
  m_pptScreen = NULL;
  m_uiPointCount = 0;

  //  copy the point data over, calculating max & min.
  if (uiPointCount>0)
  {
    m_pptData = new FLOATPOINT[uiPointCount];
    m_pptScreen = new POINT[uiPointCount];
    m_uiPointCount = uiPointCount;
    if (m_pptData!=NULL && m_pptScreen!=NULL)
    {
      UpdateData(pptData);
    }
  }
}
/////////////////////////////////////////////////////////////////////////////

CPlotData::CPlotData(const CPlotData &pd)
{
	m_szName = pd.m_szName;
  m_nStyle = pd.m_nStyle;
  m_oIsDirty = pd.m_oIsDirty;
  m_rcKeyArea = pd.m_rcKeyArea;
  m_crColor = pd.m_crColor;
  m_eXDataMin = pd.m_eXDataMin;
  m_eXDataMax = pd.m_eXDataMax;
  m_eYDataMin = pd.m_eYDataMin;
  m_eYDataMax = pd.m_eYDataMax;
  m_eYDataMean = pd.m_eYDataMean;
  m_pptData = NULL;
  m_pptScreen = NULL;
  m_uiPointCount = 0;

  //  copy the point data over, calculating max & min.
  if (pd.m_uiPointCount>0)
  {
    m_pptData = new FLOATPOINT[pd.m_uiPointCount];
    m_pptScreen = new POINT[pd.m_uiPointCount];
    m_uiPointCount = pd.m_uiPointCount;
    if (m_pptData!=NULL && m_pptScreen!=NULL)
    {
      UpdateData(pd.m_pptData);
    }
  }
}
/////////////////////////////////////////////////////////////////////////////

CPlotData& CPlotData::operator=(const CPlotData& pd)
{
	m_szName = pd.m_szName;
  m_nStyle = pd.m_nStyle;
  m_oIsDirty = pd.m_oIsDirty;
  m_rcKeyArea = pd.m_rcKeyArea;
  m_crColor = pd.m_crColor;
  m_eXDataMin = pd.m_eXDataMin;
  m_eXDataMax = pd.m_eXDataMax;
  m_eYDataMin = pd.m_eYDataMin;
  m_eYDataMax = pd.m_eYDataMax;
  m_eYDataMean = pd.m_eYDataMean;
  if (m_pptData!=NULL)
    delete[] m_pptData;
  if (m_pptScreen !=NULL)
    delete[] m_pptScreen;
  m_uiPointCount = 0;

  //  copy the point data over, calculating max & min.
  if (pd.m_uiPointCount>0)
  {
    m_pptData = new FLOATPOINT[pd.m_uiPointCount];
    m_pptScreen = new POINT[pd.m_uiPointCount];
    m_uiPointCount = pd.m_uiPointCount;
    if (m_pptData!=NULL && m_pptScreen!=NULL)
    {
      UpdateData(pd.m_pptData);
    }
  }
  return *this;
}
/////////////////////////////////////////////////////////////////////////////

bool CPlotData::UpdateData(FLOATPOINT *pptData)
{
  //  init the min and max.
  m_eXDataMin = pptData[0].x;
  m_eXDataMax = pptData[0].x;
  m_eYDataMin = pptData[0].y;
  m_eYDataMax = pptData[0].y;
  double xAccum = 0.0f;
  m_eYDataMean = 0;
  //  calc the min and max.
  for (UINT ii=0; ii<m_uiPointCount; ii++)
  {
    m_pptData[ii] = pptData[ii];
    xAccum += m_pptData[ii].y;
    m_eXDataMin = (pptData[ii].x<m_eXDataMin) ? (pptData[ii].x) : (m_eXDataMin);
    m_eXDataMax = (pptData[ii].x>m_eXDataMax) ? (pptData[ii].x) : (m_eXDataMax);
    m_eYDataMin = (pptData[ii].y<m_eYDataMin) ? (pptData[ii].y) : (m_eYDataMin);
    m_eYDataMax = (pptData[ii].y>m_eYDataMax) ? (pptData[ii].y) : (m_eYDataMax);
  }
  //  set the mean.
  m_eYDataMean = (float)( xAccum / (double)(m_uiPointCount) );
  //  force a recalc of the screen polyline.
  m_oIsDirty = true;
  return true;
}
/////////////////////////////////////////////////////////////////////////////

bool CPlotData::UpdateData(std::vector<FLOATPOINT> *pvecData)
{
  //  init the min and max.
  m_eXDataMin = pvecData->at(0).x;
  m_eXDataMax = pvecData->at(0).x;
  m_eYDataMin = pvecData->at(0).y;
  m_eYDataMax = pvecData->at(0).y;
  double xAccum = 0.0f;
  m_eYDataMean = 0;
  //  calc the min and max.
  for (UINT ii=0; ii<m_uiPointCount; ii++)
  {
    m_pptData[ii] = pvecData->at(ii);
    xAccum += m_pptData[ii].y;
    m_eXDataMin = (pvecData->at(ii).x<m_eXDataMin) ? (pvecData->at(ii).x) : (m_eXDataMin);
    m_eXDataMax = (pvecData->at(ii).x>m_eXDataMax) ? (pvecData->at(ii).x) : (m_eXDataMax);
    m_eYDataMin = (pvecData->at(ii).y<m_eYDataMin) ? (pvecData->at(ii).y) : (m_eYDataMin);
    m_eYDataMax = (pvecData->at(ii).y>m_eYDataMax) ? (pvecData->at(ii).y) : (m_eYDataMax);
  }
  //  set the mean.
  m_eYDataMean = (float)( xAccum / (double)(m_uiPointCount) );
  //  force a recalc of the screen polyline.
  m_oIsDirty = true;
  return true;
}
/////////////////////////////////////////////////////////////////////////////

CPlotData::~CPlotData()
{
  Clear();
}
/////////////////////////////////////////////////////////////////////////////

void CPlotData::Clear()
{
  //  clean up the memory.
  if (m_pptData!=NULL)
  {
    delete m_pptData;
  }
  if (m_pptScreen!=NULL)
  {
    delete m_pptScreen;
  }
}
/////////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////////
//  CLinePlot control
/////////////////////////////////////////////////////////////////////////////

BEGIN_MESSAGE_MAP(CLinePlot, CWnd)
	//{{AFX_MSG_MAP(CLinePlot)
	ON_WM_PAINT()
	ON_WM_ERASEBKGND()
	ON_WM_LBUTTONDOWN()
	ON_WM_MOUSEMOVE()
	ON_WM_LBUTTONUP()
	ON_WM_SIZE()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////

CLinePlot::CLinePlot()
{
  m_szXCaption = "X Axis";
  m_szYCaption = "Y Axis";
  m_nXIntervals = 21;
  m_nYIntervals = 10;
  m_uiXPrecision = 1;
  m_uiYPrecision = 1;
  m_eXMin = -5.0f;
  m_eXMax = 100;
  m_eYMin = -10.0f;
  m_eYMax = 10.0f;
  memset(m_prcUndoLimits, 0, LINE_PLOT_UNDO_SIZE*sizeof(FLOATRECT));
  m_nUndoIndex = -1;
  m_eXRatio = 1.0f;
  m_eYRatio = 1.0f;
  m_rcXLock.SetRectEmpty();
  m_rcYLock.SetRectEmpty();
  m_rcKeyArea.SetRectEmpty();
  m_rcPlotArea.SetRectEmpty();
  m_rcXMin.SetRectEmpty();
  m_rcXMax.SetRectEmpty();
  m_rcYMin.SetRectEmpty();
  m_rcYMax.SetRectEmpty();
  m_nCursor = -1;
  m_nSelected = -1;
  m_nMouseOverKey = -1;
  m_uiMouseOver = PLOT_OVER_NOTHING;
  m_rcZoom.SetRectEmpty();
  m_oIsMouseDown = false;
  m_oHasPassedOverPlot = false;
  m_oIsXLocked = false;
  m_oIsYLocked = false;

  //  add one to the zoom undo to start.
  FLOATRECT rcLimits;
  rcLimits.left = m_eXMin;
  rcLimits.top = m_eYMax;
  rcLimits.right = m_eXMax;
  rcLimits.bottom = m_eYMin;
  AddToZoomUndo(rcLimits);
}
/////////////////////////////////////////////////////////////////////////////

CLinePlot::~CLinePlot()
{
  Clear();
}
/////////////////////////////////////////////////////////////////////////////

BOOL CLinePlot::Create(const RECT &rect, UINT uiFlags, CWnd *pParentWnd, UINT uiID)
{
	BOOL oOkay = CWnd::Create(NULL, _T("LinePlot"), uiFlags, rect, pParentWnd, uiID);

  if (oOkay==TRUE)
  {
    //  create an edit box.
    DWORD dwEditStyle = WS_BORDER | WS_CHILD | ES_RIGHT | ES_AUTOHSCROLL; //|ES_MULTILINE
    m_edtDataEntry.Create(dwEditStyle, CRect(0, 0, 1, 1), this, ID_EDT_DATA_ENTRY);
    //  set the scale font as the font for the edit box.
    //m_edtDataEntry.SetFont(&m_fonScale);
  }

  return oOkay;
}
/////////////////////////////////////////////////////////////////////////////

BOOL CLinePlot::OnEraseBkgnd(CDC* pDC) 
{
  //  to eliminate flicker....
	return 1;
}
/////////////////////////////////////////////////////////////////////////////

void CLinePlot::DrawPlotData(CDC* pDC, UINT uiIndex, CRect &rcBounds)
{
  //  define a clipping region so we don't
  //  draw the data outside of the graph area.
  pDC->IntersectClipRect(rcBounds);

  //  should we recalculate the screen coordinates?
  if (m_lstPlotData[uiIndex].m_oIsDirty==true)
  {
    //  create the screen data points.
    double xXScreenRatio = rcBounds.Width() / (m_eXMax - m_eXMin);
    double xYScreenRatio = rcBounds.Height() / (m_eYMax - m_eYMin);
    for (UINT jj=0; jj < m_lstPlotData[uiIndex].m_uiPointCount; jj++)
    {
      m_lstPlotData[uiIndex].m_pptScreen[jj].x = rcBounds.left +
          (int)( ( m_lstPlotData[uiIndex].m_pptData[jj].x - m_eXMin ) 
          * xXScreenRatio );
      m_lstPlotData[uiIndex].m_pptScreen[jj].y = rcBounds.bottom - 
          (int)( ( m_lstPlotData[uiIndex].m_pptData[jj].y  - m_eYMin ) 
          * xYScreenRatio );
    }
    //  the screen points are up-to-date.
    m_lstPlotData[uiIndex].m_oIsDirty = false;
  }

  UINT ii = 0;
  int nLastWidth = 1;

  switch (m_lstPlotData[uiIndex].m_nStyle)
  {
    case LpLine:
    {
      //  create a pen to draw with.
      CPen penPlot(PS_SOLID, 1, m_lstPlotData[uiIndex].m_crColor);
	    CPen *ppenOld = pDC->SelectObject(&penPlot);
      //  draw the polyline
      pDC->Polyline(m_lstPlotData[uiIndex].m_pptScreen, 
          m_lstPlotData[uiIndex].m_uiPointCount);
      pDC->SelectObject(ppenOld);
      break;
    }
    case LpBar:
    {
      //  draw the bars
      RECT rcBar;
      //  save the background color.
      COLORREF crBack = pDC->GetBkColor();
      for (ii=0; ii<m_lstPlotData[uiIndex].m_uiPointCount; ii++)
      {
        //  create a rect.
        rcBar.left = (m_lstPlotData[uiIndex].m_pptScreen[ii].x<=rcBounds.left) ?
          (rcBounds.left+1) : (m_lstPlotData[uiIndex].m_pptScreen[ii].x);
        rcBar.top = m_lstPlotData[uiIndex].m_pptScreen[ii].y;
        //  the last one is a special case.
        if (ii==m_lstPlotData[uiIndex].m_uiPointCount-1)
        {
          rcBar.right = m_lstPlotData[uiIndex].m_pptScreen[ii].x + nLastWidth;
        }
        else
        {
          rcBar.right = m_lstPlotData[uiIndex].m_pptScreen[ii+1].x;
          nLastWidth = m_lstPlotData[uiIndex].m_pptScreen[ii+1].x - 
              m_lstPlotData[uiIndex].m_pptScreen[ii].x;
        }
        rcBar.bottom = rcBounds.bottom;
        //  draw the rect.
        pDC->FillSolidRect(&rcBar, m_lstPlotData[uiIndex].m_crColor);
      }
      //  restore the background color.
      pDC->SetBkColor(crBack);
      break;
    }
    default:
      break;
  }
}
/////////////////////////////////////////////////////////////////////////////

CRect CLinePlot::DrawFramework(CDC *pDC, CRect rcBounds, int nFontHeight, bool oIsPrinting)
{
	CPen *ppenOld;
	CFont *pfonOld;
  CFont fonScale;
  CFont fonHorz;
  CFont fonVert;

  //  create the fonts to use here.
  LOGFONT lf;
  GetObject(GetStockObject(DEFAULT_GUI_FONT), sizeof(LOGFONT), &lf);
  //lf.lfWeight = FW_NORMAL;
  lf.lfHeight = nFontHeight;
  fonScale.CreateFontIndirect(&lf);
  lf.lfWeight = FW_BOLD;
  lf.lfHeight *= 1.25f;
  fonHorz.CreateFontIndirect(&lf);
  lf.lfEscapement = 900;
  fonVert.CreateFontIndirect(&lf);

  //  create a white brush for the background.
  CBrush bkBrush(RGB(255, 255, 255));
  //  create a gray pen for the ruler lines.
  CPen penGray(PS_SOLID, 1, RGB(220, 220, 220));
  //  create a black brush for the data rectangle.
  CPen penBlack(PS_SOLID, 1, RGB(0, 0, 0));

  //  get the height of the caption for the font height.
  int nCaptionHeight = nFontHeight + 4;
  int nSmCaptionHeight = (int)(nCaptionHeight * 0.75f); //  GetSystemMetrics(SM_CYSMCAPTION);
  int nSpacer = nCaptionHeight/2;

  //  calculate the usable area to draw in.
  CRect rcPlotArea(rcBounds);
  rcPlotArea.DeflateRect(5, 5, 5, 5);

  //  calculate the rect for the Y caption.
  CRect rcYCaption(rcPlotArea);
  rcYCaption.right = rcYCaption.left + nCaptionHeight;

  //  calculate the rect for the X caption.
  CRect rcXCaption(rcPlotArea);
  rcXCaption.left = rcXCaption.left + nCaptionHeight;
  rcXCaption.top = rcXCaption.bottom - nCaptionHeight;

  //  adjust the client area.
  rcPlotArea.left += nCaptionHeight; // + nSpacer;
  rcPlotArea.bottom -= nCaptionHeight + nSpacer;

  //  calculate the widest rect for the y scale.
  CString szTestNumMin;
  szTestNumMin.Format("%0.f", m_eYMin);
  int nNumCharsMin = szTestNumMin.GetLength();
  CString szTestNumMax;
  szTestNumMax.Format("%0.f", m_eYMax);
  int nNumCharsMax = szTestNumMin.GetLength();
  int nNumChars = (nNumCharsMin<nNumCharsMax) ? (nNumCharsMax) : (nNumCharsMin);
  CString szScaleTest('9', nNumChars+3);
  //  use the font for the scale.
  pfonOld = pDC->SelectObject(&fonScale);
  //  calculate the nominal height and width of each scale entry.
  SIZE siScale = pDC->GetTextExtent(szScaleTest);

  //  create a starting rect for the scale.
  CRect rcStartScale(rcPlotArea);
  rcStartScale.right = rcStartScale.left + siScale.cx;
  rcStartScale.top = rcStartScale.bottom - siScale.cy;

  //  shrink the client rect by the height & width of the scale + a little more. 
  rcPlotArea.left += siScale.cx + nSpacer/2;
  rcPlotArea.bottom -= siScale.cy + nSpacer/2;
  rcPlotArea.right -= nCaptionHeight;
  rcPlotArea.top += nSpacer;

  //  create a rect to hold the stats at the top.
  CRect rcStats(rcPlotArea);
  rcStats.bottom = rcStats.top + siScale.cy;

  //  shrink the client rect away from the stats rect.
  rcPlotArea.top += siScale.cy;

  //  should we calculate a key rect?
  if (m_lstPlotData.size()>1)
  {
    //  what is the longest key?
    int nMaxKey = 5;
	  for (int ii=0; ii<m_lstPlotData.size(); ii++)
	  {
      nMaxKey = (m_lstPlotData[ii].m_szName.GetLength() > nMaxKey) ?
        (m_lstPlotData[ii].m_szName.GetLength()) : (nMaxKey);
    }
    int nKeyWidth = (nFontHeight*nMaxKey)/2;
    //  create a rect to hold the key
    m_rcKeyArea = rcPlotArea;
    m_rcKeyArea.left = m_rcKeyArea.right-nKeyWidth;
    m_rcKeyArea.right = m_rcKeyArea.right+10;

    //  center & shrink the key rect to hold only the number of plots.
    int nYCenter = (m_rcKeyArea.top + m_rcKeyArea.bottom) / 2;
    m_rcKeyArea.top = nYCenter - ((siScale.cy+4) * m_lstPlotData.size()) / 2;
    m_rcKeyArea.bottom = nYCenter + ((siScale.cy+4) * m_lstPlotData.size()) / 2;

    //  shrink the client rect away from the key rect.
    rcPlotArea.right -= (nKeyWidth+10);
    //  shrink the stats rect away from the key rect.
    rcStats.right -= (nKeyWidth+10);
  }

  //  adjust the space for the x caption so that it is centered.
  rcXCaption.left = rcPlotArea.left;
  rcXCaption.right = rcPlotArea.right;

  //  adjust the space for the y caption so that it is centered.
  rcYCaption.top = rcPlotArea.top;
  rcYCaption.bottom = rcPlotArea.bottom;

  //  calculate the rect for the min x.
  m_rcXMin = rcStartScale;
  m_rcXMin.OffsetRect(nSpacer/2, 0);
  m_rcXMin.InflateRect(2, 2, 2, 2);

  //  calculate the rect for the max x.
  m_rcXMax = rcStartScale;
  m_rcXMax.OffsetRect(rcPlotArea.right - m_rcXMax.right, 0);
  m_rcXMax.InflateRect(2, 2, 2, 2);

  //  calculate the rect for the x-locked icon in the corner.
  m_rcXLock.left = rcYCaption.left;
  m_rcXLock.top = m_rcXMin.top;
  m_rcXLock.right = rcYCaption.right;
  m_rcXLock.bottom = m_rcXMin.bottom;

  //  calculate the rect for the min y.
  m_rcYMin = rcStartScale;
  m_rcYMin.OffsetRect(0, rcPlotArea.bottom - m_rcYMin.bottom);
  m_rcYMin.InflateRect(2, 2, 2, 2);

  //  calculate the rect for the max y.
  m_rcYMax = rcStartScale;
  m_rcYMax.OffsetRect(0, rcPlotArea.top - m_rcYMax.bottom);
  m_rcYMax.InflateRect(2, 2, 2, 2);

  //  calculate the rect for the y-locked icon in the corner.
  m_rcYLock.left = m_rcYMin.right - m_rcXLock.Width();
  m_rcYLock.top = rcXCaption.top;
  m_rcYLock.right = m_rcYMin.right;
  m_rcYLock.bottom = rcXCaption.bottom;

  //  calculate how big each screen interval is.
  float eScreenXInterval = (float)(rcPlotArea.Width()) / (float)(m_nXIntervals);
  float eScreenYInterval = (float)(rcPlotArea.Height()) / (float)(m_nYIntervals);

  //  calculate how big each actual interval is.
  float eXInterval = (m_eXMax - m_eXMin) / (float)(m_nXIntervals);
  float eYInterval = (m_eYMax - m_eYMin) / (float)(m_nYIntervals);

  //  now we can calculate the ratio of actual data range 
  //  to screen area.
  m_eXRatio = (float)(rcPlotArea.Width()) / (m_eXMax - m_eXMin);
  m_eYRatio = (float)(rcPlotArea.Height()) / (m_eYMax - m_eYMin);

  //  calculate the format for the scale.
  CString szXFormat;
  szXFormat.Format("%%0.%uf", m_uiXPrecision);
  CString szYFormat;
  szYFormat.Format("%%0.%uf", m_uiYPrecision);

  /*
  ...... now start drawing!
  */

  //  fill the total background.
  pDC->FillSolidRect(&rcBounds, GetSysColor(COLOR_WINDOW));

  //  draw the sunken edge around the whole control.
  if (oIsPrinting==false)
    pDC->DrawEdge(rcBounds, EDGE_SUNKEN, BF_RECT);

  //  do we have enough room to draw?
  if (rcPlotArea.left < rcPlotArea.right)
  {

    //  draw the y caption
	  pfonOld = pDC->SelectObject(&fonVert);
    pDC->SetTextAlign(TA_CENTER | TA_BOTTOM);
    pDC->TextOut(rcYCaption.right, (rcYCaption.top+rcYCaption.bottom)/2, m_szYCaption);

    //  draw the x caption
	  pfonOld = pDC->SelectObject(&fonHorz);
    pDC->SetTextAlign(TA_CENTER | TA_BOTTOM);
    pDC->TextOut((rcXCaption.left+rcXCaption.right)/2, rcXCaption.bottom, m_szXCaption);

    //  setup to draw the scale.
    pDC->SetTextAlign(TA_RIGHT | TA_BOTTOM);
    POINT ptXScale;
    POINT ptYScale;
    CString szScale;
    pfonOld = pDC->SelectObject(&fonScale);
    ppenOld = pDC->SelectObject(&penGray);

    //  draw each x scale entry & line.
    for (int ii=0; ii<m_nXIntervals+1; ii++)
    {
      szScale.Format(szXFormat, m_eXMin+ii*eXInterval);
      ptXScale.x = (int)(rcStartScale.right+(float)(ii)*eScreenXInterval) + nSpacer/2;
      ptXScale.y = rcStartScale.bottom;
      pDC->TextOut(ptXScale.x, ptXScale.y, szScale);
      pDC->MoveTo(ptXScale.x, rcPlotArea.top);
      pDC->LineTo(ptXScale.x, rcPlotArea.bottom);
    }

    //  is the mouse over the x min?
    if (m_uiMouseOver&PLOT_OVER_X_MIN && oIsPrinting==false)
    {
      pDC->DrawEdge(m_rcXMin, BDR_RAISEDOUTER, BF_RECT);
    }

    //  is mouse over the x max?
    if (m_uiMouseOver&PLOT_OVER_X_MAX && oIsPrinting==false)
    {
      pDC->DrawEdge(m_rcXMax, BDR_RAISEDOUTER, BF_RECT);
    }

    //  draw each y scale entry & line.
    for (int jj=0; jj<m_nYIntervals+1; jj++)
    {
      szScale.Format(szYFormat, m_eYMin+jj*eYInterval);
      ptYScale.x = rcStartScale.right;
      ptYScale.y = (int)(rcStartScale.top-(float)(jj)*eScreenYInterval) - nSpacer/2;
      pDC->TextOut(ptYScale.x, ptYScale.y, szScale);
      pDC->MoveTo(rcPlotArea.left, ptYScale.y);
      pDC->LineTo(rcPlotArea.right, ptYScale.y);
    }

    //  is the mouse over the y min?
    if (m_uiMouseOver&PLOT_OVER_Y_MIN && oIsPrinting==false)
    {
      pDC->DrawEdge(m_rcYMin, BDR_RAISEDOUTER, BF_RECT);
    }

    //  is the mouse over the y max?
    if (m_uiMouseOver&PLOT_OVER_Y_MAX && oIsPrinting==false)
    {
      pDC->DrawEdge(m_rcYMax, BDR_RAISEDOUTER, BF_RECT);
    }

    pDC->SelectObject(&penBlack);

    //  draw black lines around the data.
    pDC->MoveTo(rcPlotArea.left, ptYScale.y);
    pDC->LineTo(rcPlotArea.right, ptYScale.y);
    pDC->LineTo(rcPlotArea.right, rcPlotArea.bottom);
    pDC->LineTo(rcPlotArea.left, rcPlotArea.bottom);
    pDC->LineTo(rcPlotArea.left, ptYScale.y);

    //  is the mouse over the x-lock area?
    if (m_uiMouseOver&PLOT_OVER_X_LOCK && oIsPrinting==false)
    {
      if (m_oIsMouseDown==true)
        pDC->DrawEdge(m_rcXLock, BDR_SUNKENINNER, BF_RECT);
      else
        pDC->DrawEdge(m_rcXLock, BDR_RAISEDOUTER, BF_RECT);
    }

    //  get the center of the x-lock area.
    POINT ptXLock = m_rcXLock.CenterPoint();
    //  draw the bottom of the lock.
    CRect rcXLockBottom;
    rcXLockBottom.left = ptXLock.x - 5;
    rcXLockBottom.right = ptXLock.x + 6;
    rcXLockBottom.top = ptXLock.y - 2;
    rcXLockBottom.bottom = ptXLock.y + 7;
    pDC->FillSolidRect(rcXLockBottom, RGB(0, 0, 0));
    pDC->SetBkColor(GetSysColor(COLOR_WINDOW));
    //  draw the lock top.
    pDC->MoveTo(ptXLock.x - 4, ptXLock.y);
    pDC->LineTo(ptXLock.x - 2, ptXLock.y - 7);
    pDC->LineTo(ptXLock.x + 2, ptXLock.y - 7);
    if (m_oIsXLocked==true)
      pDC->LineTo(ptXLock.x + 4, ptXLock.y);
    else
      pDC->LineTo(ptXLock.x + 4, ptXLock.y-6);

    //  is the mouse over the y-lock area?
    if (m_uiMouseOver&PLOT_OVER_Y_LOCK && oIsPrinting==false)
    {
      if (m_oIsMouseDown==true)
        pDC->DrawEdge(m_rcYLock, BDR_SUNKENINNER, BF_RECT);
      else
        pDC->DrawEdge(m_rcYLock, BDR_RAISEDOUTER, BF_RECT);
    }

    //  get the center of the y-lock area.
    POINT ptYLock = m_rcYLock.CenterPoint();
    //  draw the bottom of the lock.
    CRect rcYLockBottom;
    rcYLockBottom.left = ptYLock.x - 5;
    rcYLockBottom.right = ptYLock.x + 6;
    rcYLockBottom.top = ptYLock.y - 2;
    rcYLockBottom.bottom = ptYLock.y + 7;
    pDC->FillSolidRect(rcYLockBottom, RGB(0, 0, 0));
    pDC->SetBkColor(GetSysColor(COLOR_WINDOW));
    //  draw the lock top.
    pDC->MoveTo(ptYLock.x - 4, ptYLock.y);
    pDC->LineTo(ptYLock.x - 2, ptYLock.y - 7);
    pDC->LineTo(ptYLock.x + 2, ptYLock.y - 7);
    if (m_oIsYLocked==true)
      pDC->LineTo(ptYLock.x + 4, ptYLock.y);
    else
      pDC->LineTo(ptYLock.x + 4, ptYLock.y-6);

    //  should we draw a key?
    if (m_lstPlotData.size()>1)
    {
      //  setup to draw the keys.
      POINT ptLeftBottom;
      CString szName;
      pDC->SetTextAlign(TA_LEFT | TA_BOTTOM);
      CRect rcKey;
      CRect rcColor;
      //  draw all the keys
	    for (int ii=0; ii<m_lstPlotData.size(); ii++)
	    {
        //  get the color.
        COLORREF crKey = m_lstPlotData[ii].m_crColor;
        //  get the name.
		    szName = m_lstPlotData[ii].m_szName;
        //  calculate the bottom corner of the text.
        ptLeftBottom.x = m_rcKeyArea.left + siScale.cy + 4;
        ptLeftBottom.y = m_rcKeyArea.top + (ii+1)*(siScale.cy+4) - 2;
        //  calculate a rect around the text.
        rcKey.left = m_rcKeyArea.left;
        rcKey.right = m_rcKeyArea.right;
        rcKey.top = m_rcKeyArea.top + (ii)*(siScale.cy+4);
        rcKey.bottom = m_rcKeyArea.top + (ii+1)*(siScale.cy+4) - 1;
        m_lstPlotData[ii].m_rcKeyArea = rcKey;
        //  get a rect for the color.
        rcColor = rcKey;
        rcColor.right = rcKey.left + siScale.cy+4;
        rcColor.DeflateRect(4, 4, 4, 4);
        //  draw the color;
        pDC->FillSolidRect(&rcColor, crKey);
        pDC->SetBkColor(GetSysColor(COLOR_WINDOW));
        //  draw the key text.
        pDC->TextOut(ptLeftBottom.x, ptLeftBottom.y, szName);
        //  is this one selected?
        if (ii==m_nSelected && oIsPrinting==false)
        {
          //  draw a rect around the key.
          pDC->DrawEdge(rcKey, BDR_SUNKENINNER, BF_RECT);
        }
        //  is the mouse over a key?
        else if (ii==m_nMouseOverKey && 
            (m_uiMouseOver&PLOT_OVER_KEY) && oIsPrinting==false)
        {
          //  draw a rect around the key.
          pDC->DrawEdge(rcKey, BDR_RAISEDOUTER, BF_RECT);
        }
      }
    }

    //  draw the position & stats.
    CString szPosition;
    int nCount = m_lstPlotData.size();
    if (nCount>0 && m_nSelected>=0 && m_nSelected<nCount)
    {
      if (m_nCursor >= 0 && (UINT)(m_nCursor) < m_lstPlotData[m_nSelected].m_uiPointCount)
      {
        FLOATPOINT ptDisplay;
        ptDisplay.x = m_lstPlotData[m_nSelected].m_pptData[m_nCursor].x;
        ptDisplay.y = m_lstPlotData[m_nSelected].m_pptData[m_nCursor].y;
        szPosition.Format("Position: %0.3f    Value: %0.3f",
            ptDisplay.x, ptDisplay.y);
        pDC->SetTextAlign(TA_LEFT | TA_BOTTOM);
        pDC->TextOut(rcStats.left+1, rcStats.bottom-1, szPosition);
      }
      float eYMin = m_lstPlotData[m_nSelected].m_eYDataMin;
      float eYMax = m_lstPlotData[m_nSelected].m_eYDataMax;
      float eYMean = m_lstPlotData[m_nSelected].m_eYDataMean;
      szPosition.Format("Mean: %0.3f    Min: %0.3f    Max: %0.3f",
          eYMean, eYMin, eYMax);
      pDC->SetTextAlign(TA_RIGHT | TA_BOTTOM);
      pDC->TextOut(rcStats.right-1, rcStats.bottom-1, szPosition);
    }
  }
  
  //  delete the fonts.
  DeleteObject(&fonScale);
  DeleteObject(&fonHorz);
  DeleteObject(&fonVert);


  //  return the area we have to draw the plot in.
  return rcPlotArea;
}
/////////////////////////////////////////////////////////////////////////////

void CLinePlot::DrawData(CDC *pDC, CRect rcBounds, bool oIsPrinting) 
{
  //  draw all the plots in the correct order.
	for (int ii=0; ii<m_lstPlotData.size(); ii++)
	{
    //  if we are printing, force a recalc of the screen plot data.
    if (oIsPrinting==true)
      m_lstPlotData[ii].m_oIsDirty = true;
    DrawPlotData(pDC, (UINT)(ii), rcBounds);
    //  if we are printing, force a recalc of the screen plot data.
    if (oIsPrinting==true)
      m_lstPlotData[ii].m_oIsDirty = true;
	}

  int nCount = m_lstPlotData.size();
  CPen penDotBlack(PS_DOT, 1, RGB(0, 0, 0));
  CPen *ppenOld = pDC->SelectObject(&penDotBlack);

  //  should we draw a zoom rect?
  if (m_oIsMouseDown==true && oIsPrinting==false)
  {
    pDC->MoveTo(m_rcZoom.left, m_rcZoom.top);
    pDC->LineTo(m_rcZoom.right, m_rcZoom.top);
    pDC->LineTo(m_rcZoom.right, m_rcZoom.bottom);
    pDC->LineTo(m_rcZoom.left, m_rcZoom.bottom);
    pDC->LineTo(m_rcZoom.left, m_rcZoom.top);
  }

  //  draw the cursor position.
  else if (nCount>0 && m_nSelected>=0 && 
      m_nSelected<nCount && oIsPrinting==false)
  {
    if (m_nCursor>=0 && (UINT)(m_nCursor) < m_lstPlotData[m_nSelected].m_uiPointCount)
    {
      //  set the cursor position on the plot.
      POINT ptCursor;
      ptCursor.x = m_lstPlotData[m_nSelected].m_pptScreen[m_nCursor].x;
      ptCursor.y = m_lstPlotData[m_nSelected].m_pptScreen[m_nCursor].y;
      pDC->MoveTo(m_rcPlotArea.left, ptCursor.y);
      pDC->LineTo(m_rcPlotArea.right, ptCursor.y);
      pDC->MoveTo(ptCursor.x, rcBounds.top);
      pDC->LineTo(ptCursor.x, rcBounds.bottom);
    }
  }
  pDC->SelectObject(ppenOld);
}
/////////////////////////////////////////////////////////////////////////////

void CLinePlot::OnPaint() 
{
	CPaintDC dc(this); // device context for painting

  //  get the area to draw in.
	CRect rcClient;
	GetClientRect(&rcClient);

  //  get the height of the font to use.
  int nFontHeight = GetSystemMetrics(SM_CYCAPTION);

  //  create a memory dc for double buffering.
  CMemDC dcMem(&dc, &rcClient);

	dcMem.SaveDC();

  //  draw all the support graphics.
  m_rcPlotArea = DrawFramework(&dcMem, rcClient, nFontHeight, false);
  //  draw the plots.
  if (m_rcPlotArea.left < m_rcPlotArea.right)
  {
    DrawData(&dcMem, m_rcPlotArea, false);
  }

	dcMem.RestoreDC(-1);
}
/////////////////////////////////////////////////////////////////////////////

void CLinePlot::Print()
{
  // print dialog options
  CPrintDialog dlgPrint(FALSE,
      PD_ALLPAGES | PD_USEDEVMODECOPIES | PD_NOPAGENUMS | PD_NOSELECTION | PD_DISABLEPRINTTOFILE,
      this);

  //  show the print dialog.
  if(dlgPrint.DoModal() == IDOK)
  {
    //  get handle to printer object.
    HDC hdcPrinter = dlgPrint.GetPrinterDC();
    if (hdcPrinter!=NULL)
    {
      // get printer dc
      CDC dcPrinter;
      dcPrinter.Attach(hdcPrinter);

      //  fill the docinfo structure.
      DOCINFO diPrinter;
      ::ZeroMemory(&diPrinter, sizeof(DOCINFO));
      diPrinter.cbSize = sizeof(DOCINFO);
      diPrinter.lpszOutput = NULL;
      diPrinter.lpszDocName = m_szName.GetBuffer(1);

      if (dcPrinter.StartDoc(&diPrinter) >= 0)
      {
        // start page
        VERIFY(dcPrinter.StartPage() >= 0);

        //  get the printer width.
        int nPrinterWidth = 0;
        nPrinterWidth = dcPrinter.GetDeviceCaps(HORZRES);

        //  get the printer height.
        int nPrinterHeight = 0;
        nPrinterHeight = dcPrinter.GetDeviceCaps(VERTRES);

        //  claculate a size for the text.
        int nFontHeight = nPrinterHeight / 80.0f;

        //  get the area to draw in.
	      CRect rcClient(0, 0, nPrinterWidth, nPrinterHeight);

	      dcPrinter.SaveDC();

        //  draw all the support graphics.
        CRect rcPlotArea = DrawFramework(&dcPrinter, rcClient, nFontHeight, true);
        //  draw the plots.
        DrawData(&dcPrinter, rcPlotArea, true);

	      dcPrinter.RestoreDC(-1);

        VERIFY(dcPrinter.EndPage() >= 0);
        VERIFY(dcPrinter.EndDoc() >= 0);

      }
      else //  there was a printer error of some sort....
      {
        VERIFY(dcPrinter.AbortDoc( ) >= 0 );
        AfxMessageBox("Cannot Start Print Job.");
      }
      //  clean up
      dcPrinter.Detach();
      VERIFY( DeleteDC(hdcPrinter));
    }
  }
}
/////////////////////////////////////////////////////////////////////////////

void CLinePlot::OnSize(UINT nType, int cx, int cy) 
{
	CWnd::OnSize(nType, cx, cy);
  Refresh();
}
/////////////////////////////////////////////////////////////////////////////

void CLinePlot::Refresh()
{
  //  force a recalculation of all the plots next repaint.
	for (int ii=0; ii<m_lstPlotData.size(); ii++)
	{
    m_lstPlotData[ii].m_oIsDirty = true;
	}
  if (m_hWnd!=NULL)
    Invalidate();
}
/////////////////////////////////////////////////////////////////////////////

void CLinePlot::Clear()
{
  m_lstPlotData.clear();
  
  //  redraw if we can....
  if (m_hWnd!=NULL)
    Invalidate();

  //  nothing is selected.
  m_nSelected = -1;
}
/////////////////////////////////////////////////////////////////////////////

CPlotData &CLinePlot::operator[](UINT unIndex)
{
  return m_lstPlotData.at(unIndex);
};
/////////////////////////////////////////////////////////////////////////////

int CLinePlot::Add(CString szName, COLORREF crColor, enumPlotStyle nStyle, FLOATPOINT *pptData, UINT uiPointCount)
{
  //  create a new plot
	CPlotData PlotData(szName, crColor, (int)(nStyle), pptData, uiPointCount);
	m_lstPlotData.push_back(PlotData);
  //  change the selected one to this one.
  m_nSelected = m_lstPlotData.size()-1;
  //  should the x-axis limits be adjusted?
  if (m_oIsXLocked == false)
  {
	  if (PlotData.m_eXDataMin < m_eXMin)
      m_eXMin = PlotData.m_eXDataMin;
    if (PlotData.m_eXDataMax > m_eXMax)
      m_eXMax = PlotData.m_eXDataMax;
    for (UINT ii=0; ii < m_lstPlotData.size(); ii++)
      m_lstPlotData[ii].m_oIsDirty = true;
  }
  //  should the y-axis limits be adjusted?
  if (m_oIsYLocked == false)
  {
	  if (PlotData.m_eYDataMin < m_eYMin)
      m_eYMin = PlotData.m_eYDataMin;
    if (PlotData.m_eYDataMax > m_eYMax)
      m_eYMax = PlotData.m_eYDataMax;
    for (UINT ii=0; ii < m_lstPlotData.size(); ii++)
      m_lstPlotData[ii].m_oIsDirty = true;
  }
  if (m_hWnd!=NULL)
	  Invalidate();
  EmitSelectionChanged();
	return m_nSelected;
}
/////////////////////////////////////////////////////////////////////////////

int CLinePlot::Add(CString szName, COLORREF crColor, enumPlotStyle nStyle, std::vector<FLOATPOINT> *pvecData)
{
  //  create a new plot
	CPlotData PlotData(szName, crColor, (int)(nStyle), pvecData);
  m_lstPlotData.push_back(PlotData);
  //  change the selected one to this one.
  m_nSelected = m_lstPlotData.size()-1;
  //  should the x-axis limits be adjusted?
  if (m_oIsXLocked == false)
  {
	  if (PlotData.m_eXDataMin < m_eXMin)
      m_eXMin = PlotData.m_eXDataMin;
    if (PlotData.m_eXDataMax > m_eXMax)
      m_eXMax = PlotData.m_eXDataMax;
    for (UINT ii=0; ii < m_lstPlotData.size(); ii++)
      m_lstPlotData[ii].m_oIsDirty = true;
  }
  //  should the y-axis limits be adjusted?
  if (m_oIsYLocked == false)
  {
	  if (PlotData.m_eYDataMin < m_eYMin)
      m_eYMin = PlotData.m_eYDataMin;
    if (PlotData.m_eYDataMax > m_eYMax)
      m_eYMax = PlotData.m_eYDataMax;
    for (UINT ii=0; ii < m_lstPlotData.size(); ii++)
      m_lstPlotData[ii].m_oIsDirty = true;
  }
  if (m_hWnd!=NULL)
	  Invalidate();
  EmitSelectionChanged();
	return m_nSelected;
}
/////////////////////////////////////////////////////////////////////////////

bool CLinePlot::Remove(int nIndex)
{
  //  remove a plot.
  bool oRetVal = false;
  if (nIndex<0 || nIndex>=m_lstPlotData.size())
    return false;
  else
  {
    m_lstPlotData.erase(m_lstPlotData.begin()+nIndex);
    //  change the selected by one, if necessary.
    if (m_nSelected >= nIndex)
    {
      m_nSelected--;
      int nMaxIndex = m_lstPlotData.size() - 1;
      //  make sure the selection makes sense.
      if (m_nSelected > nMaxIndex)
        m_nSelected = nMaxIndex;
      else if (m_nSelected < 0)
        m_nSelected = 0;
      EmitSelectionChanged();
    }
    if (m_hWnd!=NULL)
	    Invalidate();
    return true;
  }
}
/////////////////////////////////////////////////////////////////////////////

void CLinePlot::OnMouseMove(UINT nFlags, CPoint point) 
{
  UINT uiMouseOver = PLOT_OVER_NOTHING;
  int nMouseOverKey = -1;

  //  are we drawing a zoom rect?
  if (m_oIsMouseDown==true)
  {
    if (m_rcPlotArea.PtInRect(point)==TRUE)
      m_oHasPassedOverPlot = true;
    m_rcZoom.right = point.x;
    m_rcZoom.bottom = point.y;
    if (m_hWnd!=NULL)
      Invalidate();
  }

  //  is the mouse over the x min button?
  else if (m_rcXMin.PtInRect(point)==TRUE)
  {
    uiMouseOver |= PLOT_OVER_X_MIN;
  }
  //  is the mouse over the x max button?
  if (m_rcXMax.PtInRect(point)==TRUE)
  {
    uiMouseOver |= PLOT_OVER_X_MAX;
  }
  //  is the mouse over the y min button?
  if (m_rcYMin.PtInRect(point)==TRUE)
  {
    uiMouseOver |= PLOT_OVER_Y_MIN;
  }
  //  is the mouse over the y max button?
  if (m_rcYMax.PtInRect(point)==TRUE)
  {
    uiMouseOver |= PLOT_OVER_Y_MAX;
  }
  //  is the mouse over the x-lock button?
  else if (m_rcXLock.PtInRect(point)==TRUE)
  {
    uiMouseOver |= PLOT_OVER_X_LOCK;
  }
  //  is the mouse over the y-lock button?
  else if (m_rcYLock.PtInRect(point)==TRUE)
  {
    uiMouseOver |= PLOT_OVER_Y_LOCK;
  }
  //  is the mouse over the key area?
  nMouseOverKey = -1;
  CRect rcKey;
  for (int ii=0; ii<m_lstPlotData.size(); ii++)
	{
		rcKey = m_lstPlotData[ii].m_rcKeyArea;
    if (rcKey.PtInRect(point)==TRUE)
    {
      uiMouseOver |= PLOT_OVER_KEY;
      nMouseOverKey = ii;
      break;
    }
  }

  //  is the mouse over the plot area?
  if (m_rcPlotArea.PtInRect(point)==TRUE)
  {
    m_uiMouseOver |= PLOT_OVER_DATA;
    //  get the nearest data point
    int nCount = m_lstPlotData.size();
    if (nCount>0 && m_nSelected>=0 && m_nSelected<nCount)
    {
      int jj = 0;
      while (m_lstPlotData[m_nSelected].m_pptScreen[jj].x < point.x)
        jj++;
      //  set the current cursor data point.
      m_nCursor = jj;
      EmitMouseMove();      
    }
  }

  if (uiMouseOver!=m_uiMouseOver || nMouseOverKey!=m_nMouseOverKey)
  {
    m_uiMouseOver = uiMouseOver;
    m_nMouseOverKey = nMouseOverKey;
    //  redraw the data.
    if (m_hWnd!=NULL)
      Invalidate();
  }
  CWnd::OnMouseMove(nFlags, point);
}
/////////////////////////////////////////////////////////////////////////////

void CLinePlot::OnLButtonDown(UINT nFlags, CPoint point) 
{
  //  setup to draw the zoom rect.
  m_rcZoom.left = point.x;
  m_rcZoom.top = point.y;
  m_oIsMouseDown = true;

  //  is the mouse over a key?
  if (m_nMouseOverKey>=0 && (m_uiMouseOver&PLOT_OVER_KEY))
  {
    if (m_nSelected!=m_nMouseOverKey)
    {
      m_nSelected = m_nMouseOverKey;
      if (m_hWnd!=NULL)
        Invalidate();
      // let the parent know that the selection changed.
      EmitSelectionChanged();
    }
  }

  //  is the mouse over the x-lock button?
  else if (m_uiMouseOver&PLOT_OVER_X_LOCK)
  {
    if (m_hWnd!=NULL)
      Invalidate();
  }
  //  is the mouse over the y-lock button?
  else if (m_uiMouseOver&PLOT_OVER_Y_LOCK)
  {
    if (m_hWnd!=NULL)
      Invalidate();
  }

	CWnd::OnLButtonDown(nFlags, point);
}
/////////////////////////////////////////////////////////////////////////////

void CLinePlot::OnLButtonUp(UINT nFlags, CPoint point) 
{
  CString szFormat;
  CString szData;

  //  are we drawing a zoom rect?
  if (m_oIsMouseDown==true && m_oHasPassedOverPlot==true)
  {
    //  we are no longer drawing a zoom rect.
    m_rcZoom.right = point.x;
    m_rcZoom.bottom = point.y;

    //  was it drawn backwards or forwards?
    if (m_rcZoom.bottom<m_rcZoom.top)
    {
      FLOATRECT rcLimits = GetLastZoomUndo();
      m_eXMin = rcLimits.left;
      m_eYMax = rcLimits.top;
      m_eXMax = rcLimits.right;
      m_eYMin = rcLimits.bottom;

      //  force a recalculation of all the plots next repaint.
      Refresh();
      //  tell the parent that the limits are different.
      EmitLimitsChanged();

    }
    else
    {
      //  save the current limits to the undo list.
      FLOATRECT rcLimits;
      rcLimits.left = m_eXMin;
      rcLimits.top = m_eYMax;
      rcLimits.right = m_eXMax;
      rcLimits.bottom = m_eYMin;
      AddToZoomUndo(rcLimits);

      //  if the x-axis is locked, do nothing.
      if (m_oIsXLocked==false)
      {
        //  determine the ratio of the ranges.
        //  assume the inside dimensions are correct.
        float eXDataRange = m_eXMax - m_eXMin + 1.0f;
        float eXPixelRange = (float)(m_rcPlotArea.Width());
        float eXRatio = eXDataRange / eXPixelRange;

        //  get distance of left edge of rect from inside left edge.
        float eXNewMin = m_eXMin + (m_rcZoom.left - m_rcPlotArea.left) * eXRatio;
        //  make sure the new min is not less than the current min.
        eXNewMin = (eXNewMin<m_eXMin) ? (m_eXMin) : (eXNewMin);

        //  get distance of right edge of rect from inside left edge.
        float eXNewMax = m_eXMin + (m_rcZoom.right - m_rcPlotArea.left) * eXRatio;
        //  make sure the new max is not greater than the current min.
        eXNewMax = (eXNewMax>m_eXMax) ? (m_eXMax) : (eXNewMax);

        //  assign to variables
        m_eXMin = eXNewMin;
        m_eXMax = eXNewMax;
      }

      //  if the y-axis is locked, do nothing.
      if (m_oIsYLocked==false)
      {
        //  determine the ratio of the ranges.
        //  assume the inside dimensions are correct.
        float eYDataRange = m_eYMax - m_eYMin + 1.0f;
        float eYPixelRange = (float)(m_rcPlotArea.Height());
        float eYRatio = eYDataRange / eYPixelRange;

        //  get distance of bottom edge of rect from inside bottom edge.
        float eYNewMin = m_eYMin + (m_rcPlotArea.bottom - m_rcZoom.bottom) * eYRatio;
        //  make sure the new min is not less than the current min.
        eYNewMin = (eYNewMin<m_eYMin) ? (m_eYMin) : (eYNewMin);

        //  get distance of right edge of rect from inside left edge.
        float eYNewMax = m_eYMin + (m_rcPlotArea.bottom - m_rcZoom.top) * eYRatio;
        //  make sure the new max is not greater than the current min.
        eYNewMax = (eYNewMax>m_eYMax) ? (m_eYMax) : (eYNewMax);

        //  assign to variables
        m_eYMin = eYNewMin;
        m_eYMax = eYNewMax;
      }

      //  force a recalculation all the plots next time.
      Refresh();
      //  tell the parent that the limits are different.
      EmitLimitsChanged();
    }
  }

  //  not drawing a zoom rect now.
  m_oIsMouseDown = false;
  m_oHasPassedOverPlot = false;

  //  is the mouse over the x min?
  if ((m_uiMouseOver & PLOT_OVER_X_MIN) && m_oIsXLocked!=true)
  {
    szFormat.Format("%%0.%uf", m_uiXPrecision);
    szData.Format(szFormat, m_eXMin);
    m_edtDataEntry.SetWindowText(szData);
    m_edtDataEntry.MoveWindow(m_rcXMin);
    m_edtDataEntry.ShowWindow(TRUE);
    m_edtDataEntry.SetFocus();
  }

  //  is the mouse over the x max?
  else if ((m_uiMouseOver & PLOT_OVER_X_MAX) && m_oIsXLocked!=true)
  {
    szFormat.Format("%%0.%uf", m_uiXPrecision);
    szData.Format(szFormat, m_eXMax);
    m_edtDataEntry.SetWindowText(szData);
    m_edtDataEntry.MoveWindow(m_rcXMax);
    m_edtDataEntry.ShowWindow(TRUE);
    m_edtDataEntry.SetFocus();
  }

  //  is the mouse over the y min?
  else if ((m_uiMouseOver & PLOT_OVER_Y_MIN) && m_oIsYLocked!=true)
  {
    szFormat.Format("%%0.%uf", m_uiYPrecision);
    szData.Format(szFormat, m_eYMin);
    m_edtDataEntry.SetWindowText(szData);
    m_edtDataEntry.MoveWindow(m_rcYMin);
    m_edtDataEntry.ShowWindow(TRUE);
    m_edtDataEntry.SetFocus();
  }

  //  is the mouse over the y max?
  else if ((m_uiMouseOver & PLOT_OVER_Y_MAX) && m_oIsYLocked!=true)
  {
    szFormat.Format("%%0.%uf", m_uiYPrecision);
    szData.Format(szFormat, m_eYMax);
    m_edtDataEntry.SetWindowText(szData);
    m_edtDataEntry.MoveWindow(m_rcYMax);
    m_edtDataEntry.ShowWindow(TRUE);
    m_edtDataEntry.SetFocus();
  }

  //  is the mouse over the x-lock button?
  else if (m_uiMouseOver&PLOT_OVER_X_LOCK)
  {
    m_oIsXLocked = !m_oIsXLocked;
    if (m_hWnd!=NULL)
      Invalidate();
  }
  //  is the mouse over the y-lock button?
  else if (m_uiMouseOver&PLOT_OVER_Y_LOCK)
  {
    m_oIsYLocked = !m_oIsYLocked;
    if (m_hWnd!=NULL)
      Invalidate();
  }

  else
  {
    //  if there is an edit window visible, hide it
    HideEditWindow();
    if (m_hWnd!=NULL)
      Invalidate();
  }

  CWnd::OnLButtonUp(nFlags, point);
}
/////////////////////////////////////////////////////////////////////////////
    
void CLinePlot::EmitSelectionChanged()
{
  CWnd *pwndParent = GetParent();
  NMHDR msgNotif;
  msgNotif.code = NM_PLOT_SEL_CHANGE;
  msgNotif.hwndFrom = this->GetSafeHwnd();
  msgNotif.idFrom = this->GetDlgCtrlID();
  pwndParent->SendMessage(WM_NOTIFY, 0, (UINT)&msgNotif);
}
/////////////////////////////////////////////////////////////////////////////
    
void CLinePlot::EmitLimitsChanged()
{
  CWnd *pwndParent = GetParent();
  NMHDR msgNotif;
  msgNotif.code = NM_PLOT_LIMITS_CHANGE;
  msgNotif.hwndFrom = this->GetSafeHwnd();
  msgNotif.idFrom = this->GetDlgCtrlID();
  pwndParent->SendMessage(WM_NOTIFY, 0, (UINT)&msgNotif);
}
/////////////////////////////////////////////////////////////////////////////
    
void CLinePlot::EmitMouseMove()
{
  CWnd *pwndParent = GetParent();
  NMHDR msgNotif;
  msgNotif.code = NM_PLOT_MOUSE_MOVE;
  msgNotif.hwndFrom = this->GetSafeHwnd();
  msgNotif.idFrom = this->GetDlgCtrlID();
  pwndParent->SendMessage(WM_NOTIFY, 0, (UINT)&msgNotif);
}
/////////////////////////////////////////////////////////////////////////////

void CLinePlot::AddToZoomUndo(FLOATRECT &rcLimits)
{
  //  is the undo list full?
  if (m_nUndoIndex==LINE_PLOT_UNDO_SIZE-1)
  {
    //  move everything back one...
    for (UINT ii = 0; ii<LINE_PLOT_UNDO_SIZE-1; ii++)
    {
      m_prcUndoLimits[ii] = m_prcUndoLimits[ii+1];
    }
    //  subtract one from the index so it is now correct.
    m_nUndoIndex--;
  }
  //  increment the index to store the new undo.
  m_nUndoIndex++;
  m_prcUndoLimits[m_nUndoIndex] = rcLimits;
}
/////////////////////////////////////////////////////////////////////////////

FLOATRECT CLinePlot::GetLastZoomUndo()
{
  int nCurrIndex = m_nUndoIndex;

  //  are we on the first one?
  m_nUndoIndex = (m_nUndoIndex>0) ? 
    (m_nUndoIndex-1) : (m_nUndoIndex);

  return m_prcUndoLimits[nCurrIndex];
}
/////////////////////////////////////////////////////////////////////////////
    
void CLinePlot::HideEditWindow()
{
  //  is the edit window visible?
  if (m_edtDataEntry.IsWindowVisible()==TRUE)
  {
    //  hide the edit window.
    m_edtDataEntry.ShowWindow(FALSE);

    //  get the value from the window.
    CString szText;
    m_edtDataEntry.GetWindowText(szText);
    float eValue  = (float)(atof(szText.GetBuffer(1)));

    //  where is its center?
    CRect rcWindow;
    m_edtDataEntry.GetWindowRect(&rcWindow);
    ScreenToClient(&rcWindow);
    CPoint ptCenter = rcWindow.CenterPoint();

    //  based on the center, update the correct min or max.
    if (m_rcXMin.PtInRect(ptCenter)==TRUE)
    {
      m_eXMin = eValue;
      EmitLimitsChanged();
    }
    else if (m_rcXMax.PtInRect(ptCenter)==TRUE)
    {
      m_eXMax = eValue;
      EmitLimitsChanged();
    }
    else if (m_rcYMin.PtInRect(ptCenter)==TRUE)
    {
      m_eYMin = eValue;
      EmitLimitsChanged();
    }
    else if (m_rcYMax.PtInRect(ptCenter)==TRUE)
    {
      m_eYMax = eValue;
      EmitLimitsChanged();
    }

    Refresh();
  }
}
/////////////////////////////////////////////////////////////////////////////

CString CLinePlot::GetName(int nIndex)
{
	return m_lstPlotData[nIndex].m_szName;
}
/////////////////////////////////////////////////////////////////////////////

bool CLinePlot::SetName(int nIndex, CString szName)
{
  m_lstPlotData[nIndex].m_szName = szName;
  if (m_hWnd!=NULL)
	  Invalidate();
  return true;
}
/////////////////////////////////////////////////////////////////////////////

CLinePlot::enumPlotStyle CLinePlot::GetStyle(int nIndex)
{
	return (CLinePlot::enumPlotStyle)(m_lstPlotData[nIndex].m_nStyle);
}
/////////////////////////////////////////////////////////////////////////////

bool CLinePlot::SetStyle(int nIndex, enumPlotStyle nStyle)
{
  m_lstPlotData[nIndex].m_nStyle = nStyle;
  if (m_hWnd!=NULL)
	  Invalidate();
  return true;
}
/////////////////////////////////////////////////////////////////////////////

COLORREF CLinePlot::GetColor(int nIndex)
{
	return m_lstPlotData[nIndex].m_crColor;
}
/////////////////////////////////////////////////////////////////////////////

bool CLinePlot::SetColor(int nIndex, COLORREF crColor)
{
  m_lstPlotData[nIndex].m_crColor = crColor;
  if (m_hWnd!=NULL)
	  Invalidate();
  return true;
}
/////////////////////////////////////////////////////////////////////////////

FLOATPOINT *CLinePlot::GetData(int nIndex)
{
  return m_lstPlotData[nIndex].m_pptData;
}
/////////////////////////////////////////////////////////////////////////////

bool CLinePlot::SetData(int nIndex, FLOATPOINT *pptData)
{
  m_lstPlotData[nIndex].UpdateData(pptData);
  if (m_hWnd!=NULL)
	  Invalidate();
  return true;
}
/////////////////////////////////////////////////////////////////////////////
	  
UINT CLinePlot::GetPointCount(int nIndex)
{
  return m_lstPlotData[nIndex].m_uiPointCount;
}
/////////////////////////////////////////////////////////////////////////////

bool CLinePlot::SetXMin(float &eXMin) 
{ 
  if (m_oIsXLocked==true)
    return false;
  else
  {
    if (m_eXMin!=eXMin)
    {
      m_eXMin = eXMin;
      EmitLimitsChanged();
      if (m_hWnd!=NULL)
	      Invalidate();
    }
    return true;
  }
}
/////////////////////////////////////////////////////////////////////////////

bool CLinePlot::SetXMax(float &eXMax) 
{ 
  if (m_oIsXLocked==true)
    return false;
  else
  {
    if (m_eXMax!=eXMax)
    {
      m_eXMax = eXMax;
      EmitLimitsChanged();
      if (m_hWnd!=NULL)
	      Invalidate();
    }
    return true;
  }
}
/////////////////////////////////////////////////////////////////////////////

bool CLinePlot::SetYMin(float &eYMin) 
{ 
  if (m_oIsYLocked==true)
    return false;
  else
  {
    if (m_eYMin!=eYMin)
    {
      m_eYMin = eYMin;
      EmitLimitsChanged();
      if (m_hWnd!=NULL)
	      Invalidate();
    }
    return true;
  }
}
/////////////////////////////////////////////////////////////////////////////

bool CLinePlot::SetYMax(float &eYMax) 
{ 
  if (m_oIsYLocked==true)
    return false;
  else
  {
    if (m_eYMax!=eYMax)
    {
      m_eYMax = eYMax;
      EmitLimitsChanged();
      if (m_hWnd!=NULL)
	      Invalidate();
    }
    return true;
  }
}
/////////////////////////////////////////////////////////////////////////////

bool CLinePlot::SetIsXLocked(bool &oLock)
{ 
  if (m_oIsXLocked!=oLock)
  {
    m_oIsXLocked = oLock; 
    if (m_hWnd!=NULL)
	    Invalidate();
  }
  return true;
}
/////////////////////////////////////////////////////////////////////////////

bool CLinePlot::SetIsYLocked(bool &oLock)
{
  if (m_oIsYLocked!=oLock)
  {
    m_oIsYLocked = oLock; 
    if (m_hWnd!=NULL)
	    Invalidate();
  }
  return true;
}
/////////////////////////////////////////////////////////////////////////////